#include "FileWrapper.h"

const char* clusterColors[] = { "NON_COLOR", "#0000FF", "#8A2BE2", "#006400",
    "#A52A2A", "#FF4500", "#FF69B4", "#7FFF00", "#00FFFF","#FFD700", "#C0C0C0" };

/*
 * create pointer and open file with the given permissions
 */
FILE *openFileFromDir(const char* directory,const char * fileName ,const char* permissions)
{
    char buffer[1000];
	FILE *file;
    /* concatenate directory and file name */
	strcpy(buffer, directory);
	strcat(buffer, fileName);
	file = fopen(buffer, permissions);
	if (file == NULL) {
		fprintf(stderr,"Error: standard function fopen has failed.\n");
		return NULL;
	}
    
	return file;
}

/*
 * parse double from str in .3f format
 */
double parseDouble(char* doubleStr,int allowZero)
{
    double weight;
    weight = atof(doubleStr);
	if(weight == 0 && !allowZero)
	{
		return -1;
	}
    sprintf(doubleStr,"%.3f",weight);
    weight = atof(doubleStr);
    if(weight < 0 || weight > 1)
    {
        return -1;
    }
    if(weight == 0 && !allowZero)
	{
		return -1;
	}
    return weight;
}

/*
 * fill graph from network file
 */
int fillGraph(graph* gr,const char* directory)
{
    FILE *inFile;
    char *vertexName;
    char weightStr[MAX_LINE_SIZE +2];
    int vertex1Id;
    int vertex2Id;
    double weight;
    char commandLine[MAX_LINE_SIZE +2] = {0};
    inFile = openFileFromDir(directory, INPUT_FILE_NAME, "r");
    if(inFile == NULL)
        return ERROR;
    vertexName = malloc(MAX_LINE_SIZE * sizeof(char));
    if(vertexName == NULL)
    {
        fprintf(stderr,"Error: standard function malloc has failed");
        fclose(inFile);
        return ERROR;
    }
	while(1){
		if(fgets(commandLine,sizeof(commandLine),inFile)==NULL){ /*This function reads up to sizeof(fullcommand)-1 characters, or until it encounters the new line character. The function adds '\0' to the end of the array */
            if(!feof(inFile))
                fprintf(stderr,"Error: standard function fgets has failed"); /* error case */
			break; /* breaks the while(1) loop */
		}
        if(commandLine[sizeof(commandLine)-2]!='\n'&&strlen(commandLine)==MAX_LINE_SIZE+1){ /*# of characters (excluding '\n') in command_line is <=num_char iff the condition takes place. */
			fprintf(stderr,"Error: command length must be less than or equal to %d characters\n",MAX_LINE_SIZE);
			while(getchar()!='\n'){} /* read the remaining characters */
			continue;/* continue with  while(1) loop */
		}
        /*check if the current command is add vertex and get the vertex name */
        if(sscanf(commandLine,"add_vertex %s", vertexName) ==1)
        {
            if(add_vertex(gr, vertexName) ==ERROR)
            {
                fclose(inFile);
                free(vertexName);
                return ERROR;
            }
        }
        /*check if the command is add edge and check the parameters type */
        else if(sscanf(commandLine, "add_edge %d %d %s", &vertex1Id,&vertex2Id,weightStr) == 3)
        {
            /*parse the weight according to the 3f format*/
            weight = parseDouble(weightStr,FALSE);
            if(weight != -1)
            {
                if(add_edge(gr,vertex1Id,vertex2Id,weight) == ERROR)
                {
                    fclose(inFile);
                    free(vertexName);
                    return ERROR;
                }
            }
            else{
                fprintf(stderr,"Error: unable to add edge %d-%d , weight is not in the correct format/range",vertex1Id,vertex2Id);
            }
        }
        else
        {
			fprintf(stderr,"Error: unknown command");
        }
    }
    fclose(inFile);
    free(vertexName);
	return OK;
}

/*
 * write solution to results file
 */
int writeResults(graph* gr,const char* directory,double avgEdgeIn,double avgEdgeBetween,double score)
{
    FILE *resFile;
    int i;
    resFile = openFileFromDir(directory, RESULTS_FILE_NAME,"w");
    if(resFile == NULL)
        return ERROR;
    
	fprintf(resFile, "Cluster Editing Optimal Solution\n");
	fprintf(resFile, "score: %.3f\n",gr -> totalWeight -  score );
	fprintf(resFile, "number of clusters: %d\n", gr -> numOfClusters);
	fprintf(resFile, "cost for ghost: %.3f\n\n", gr -> defaultWeigth);
	fprintf(resFile, "The clustered network:\n");
	printNetwork(gr,resFile);
	fprintf(resFile, "Clustering statistics for the %d clusters:\n",
			gr -> numOfClusters);
	fprintf(resFile, "Average weight of an edge within clusters: %.3f\n",
			avgEdgeIn);
	fprintf(resFile, "Average weight of an edge between clusters: %.3f\n",
			avgEdgeBetween);
    if(gr -> numofVertices)
    {
        if (gr -> numofVertices == 1)
            fprintf(resFile, "Cluster 1 score: 0.000 diameter: 0.000\n");
        else {
            for (i = 0; i < gr->numOfClusters; i++) {
            	if (gr->clusters[i] -> diameter == INT_MAX){
            		fprintf(resFile, "Cluster %d score: %.3f diameter: %s\n", i + 1, gr -> clusters[i] ->score, "inf");
            	} else {
            		fprintf(resFile, "Cluster %d score: %.3f diameter: %d\n", i + 1, gr -> clusters[i] ->score, gr->clusters[i] -> diameter);
            	}
            }
        }
    }
    return OK;
}

/*
 * print graph vertices and edges to results file
 */
void printNetwork(graph* gr,FILE* resFile)
{
    edgeListItem* firstItem;
    edgeListItem* currItem;
    edge* currEdge;
    int i;
    if(gr -> numofVertices >0)
    {
        fprintf(resFile, "%d vertices:\n",gr -> numofVertices);
        for (i =0; i< gr -> numofVertices; i++) {
            fprintf(resFile,"%s %d\n",gr-> vertices[i] -> name,gr-> vertices[i] -> cluster -> id);
        }
        if(gr -> numOfEdges > 0)
        {
            fprintf(resFile, "%d edges:\n",gr -> numOfEdges);
            for (i =0; i< gr -> numofVertices; i++) {
                firstItem = gr -> vertices[i] -> edges ->head;
                if(firstItem ==NULL)
                    continue;
                currItem = firstItem;
                do {
                    currEdge = currItem ->edgeData;
                    if(currEdge -> vertex1 -> id >= i &&
                       currEdge -> vertex2 -> id >= i) /*if this edge was already printed then one the vertexID is smaller then i while the other is equal to i*/
                        fprintf(resFile,"%s-%s %.3f\n",currEdge ->vertex1 -> name,currEdge ->vertex2 -> name,currEdge -> weight);
                    currItem = currItem -> next;
                } while (currItem != firstItem);
            }
        }
        fprintf(resFile,"\n");
    }
}

/*
 * add node to the xml with vertex data
 */
int addVertexToXml(xmlNodePtr root, vertex* vertex)
{
    char tmpVertexId[100];
    xmlNodePtr node;
    node=xmlNewChild(root,NULL,BAD_CAST "node",NULL);
    if (node == NULL) {
        printf("Error: failed to create XML child node\n");
        return ERROR;
    }
    xmlNewProp(node,BAD_CAST "label",BAD_CAST vertex -> name);
    if (sprintf(tmpVertexId, "%d", vertex -> id) < 0) { /* tmp gets vertex id. */
        fprintf(stderr,"Error: standard function sprintf has failed\n");
        return ERROR;
    }
    xmlNewProp(node,BAD_CAST "id",BAD_CAST tmpVertexId);
    
    node=xmlNewChild(node,NULL,BAD_CAST "graphics",NULL);
    if (node == NULL) {
        printf("Error: failed to create XML child node\n");
        return ERROR;
    }
    xmlNewProp(node,BAD_CAST "type",BAD_CAST "ELLIPSE");
    xmlNewProp(node, BAD_CAST "fill", BAD_CAST getClusterColor(vertex -> cluster-> id));
    xmlNewProp(node,BAD_CAST "width",BAD_CAST "5");
    xmlNewProp(node,BAD_CAST "cy:nodeLabel",BAD_CAST vertex -> name);
    xmlNewProp(node,BAD_CAST "cy:borderLineType",BAD_CAST "solid");
    return OK;
}

/*
 * add node with to xml with the current edge data
 */
int addEdgeToXML(xmlNodePtr root, edge *currEdge)
{
    char tmpVertexId[100];
    char tmpEdgeName[1024];
    xmlNodePtr node;
    node=xmlNewChild(root,NULL,BAD_CAST "edge",NULL);
    if (node == NULL) {
        printf("Error: failed to create XML child node\n");
        return ERROR;
    }
    /* concat edge name */
    strcpy(tmpEdgeName, currEdge ->vertex1 -> name);
    strcat(tmpEdgeName, " (pp) ");
    strcat(tmpEdgeName, currEdge -> vertex2 -> name);
    xmlNewProp(node,BAD_CAST "label",BAD_CAST tmpEdgeName);
    if (sprintf(tmpVertexId, "%d", currEdge -> vertex1 -> id) < 0) {
        fprintf(stderr,"Error: standard function sprintf has failed\n");
        return ERROR;
    }
    xmlNewProp(node,BAD_CAST "source",BAD_CAST tmpVertexId);
    if (sprintf(tmpVertexId, "%d", currEdge -> vertex2 -> id) < 0) {
        fprintf(stderr,"Error: standard function sprintf has failed\n");
        return ERROR;
    }
    xmlNewProp(node,BAD_CAST "target",BAD_CAST tmpVertexId);
    
    node=xmlNewChild(node,NULL,BAD_CAST "graphics",NULL);
    if (node == NULL) {
        printf("Error: failed to create XML child node\n");
        return ERROR;
    }
    if (sprintf(tmpEdgeName, "weight=%.3f", currEdge -> weight) < 0) {
        fprintf(stderr,"Error: standard function sprintf has failed\n");
        return ERROR;
    }
    xmlNewProp(node,BAD_CAST "cy:edgeLabel",BAD_CAST tmpEdgeName);
    return OK;
}

/*
 * create the general properties of xgmml format
 */
void addXGMMLstruct(const char *fileName, xmlNodePtr root)
{
    xmlNewProp(root, BAD_CAST "label", BAD_CAST fileName);
	xmlNewProp(root, BAD_CAST "directed", BAD_CAST "0");
	xmlNewProp(root, BAD_CAST "xmlns:cy", BAD_CAST "http://www.cytoscape.org");
	xmlNewProp(root, BAD_CAST "xmlns:dc", BAD_CAST "http://purl.org/dc/elements/1.1/");
	xmlNewProp(root, BAD_CAST "xmlns:rdf", BAD_CAST "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
	xmlNewProp(root, BAD_CAST "xmlns:xlink", BAD_CAST "http://www.w3.org/1999/xlink");
	xmlNewProp(root, BAD_CAST "xmlns", BAD_CAST "http://www.cs.rpi.edu/XGMML");
}

/*
 * save xml file to disc
 */
int save_xml_doc(xmlDocPtr file,const char *name,const char* outDir)
{
	char filename[2048];
	int ret;
    /*  concat the xgmml file name include dir*/
	strcpy(filename, outDir);
	strcat(filename, name);
    strcat(filename, XML_SUFFIX);
	ret = xmlSaveFileEnc(filename, file, "UTF-8");
	if (ret == -1) {
		printf("Error: failed to save XML document %s\n", name);
		return ERROR;
	}
	return OK;
}

/*
 * free xml doc and clean memory
 */
void freeXml(xmlDocPtr file)
{
	xmlFreeDoc(file);
	xmlCleanupParser();
	xmlMemoryDump();
}

/*
 * write graph to xgmml file (decide wether to write all clusters or just 5 by file name)
 */
int writeXml(graph* gr,const char* outDir,const char* fileName)
{
    xmlNodePtr root;
	xmlDocPtr xmlDoc;
    edgeListItem* firstItem;
    edgeListItem* currItem;
    edge* currEdge;
    
    int numberOfClustersToWrite;
    int i;
    xmlDoc = xmlNewDoc(BAD_CAST "1.0");
	if (xmlDoc == NULL) {
		fprintf(stderr,"Error: failed to create XML document\n");
		return ERROR;
	}
	root = xmlNewNode(NULL, BAD_CAST "graph");
	if (root == NULL) {
		fprintf(stderr,"Error: failed to create XML root node\n");
		return ERROR;
	}
    numberOfClustersToWrite = strcmp(fileName, BEST_CLUSTERS_FILE) == 0 ? 5 : gr -> numOfClusters;
    
	addXGMMLstruct(fileName, root);
	xmlDocSetRootElement(xmlDoc, root);
	root = xmlDocGetRootElement(xmlDoc);
    
    for (i=0;i< gr -> numofVertices; i++) {
        if(gr -> vertices[i] -> cluster -> id > numberOfClustersToWrite)
            continue;
        if(addVertexToXml(root, gr -> vertices[i]) == ERROR)
            return ERROR;
    }
    for (i =0; i< gr -> numofVertices; i++) {
        if(gr -> vertices[i] -> cluster -> id > numberOfClustersToWrite)
            continue;
        firstItem = gr -> vertices[i] -> edges ->head;
        if(firstItem ==NULL)
            continue;
        currItem = firstItem;
        do {
            currEdge = currItem ->edgeData;
            if(currEdge ->vertex1 -> id >=i &&
               currEdge -> vertex2 -> id >= i &&
               currEdge -> vertex2 ->cluster -> id <= numberOfClustersToWrite &&
               currEdge -> vertex1 ->cluster -> id <= numberOfClustersToWrite)
            	/* check if the vertex already printed and if both of the edge vertices are in the file*/
            {
                if(addEdgeToXML(root, currEdge) == ERROR)
                    return ERROR;
            }
            currItem = currItem -> next;
        } while (currItem != firstItem);
    }
    if(save_xml_doc(xmlDoc,fileName,outDir) == -1)
    {
        printf("Error: failed to save XML document %s\n", fileName);
        return ERROR;
    }
    freeXml(xmlDoc);
    return OK;
}

/*
 * get cluster color by id
 */
const char *getClusterColor(int id)
{
    if(id >= 10)
    {
        return clusterColors[10];
    }
    return clusterColors[id];
}
